import random as rd
import numpy as np
import copy as cp


def change(old, low, high):  # 随机变异
    if low == high:
        return old
    r = rd.random()
    if r > 0.5:
        return old + 1 if old != high else old - 1
    else:
        return old - 1 if old != low else old + 1


class Genetic:
    def __init__(self, pop_num, mut_prob, elite_num, limit):
        self.pop_num = pop_num  # 种群数量
        self.mut_prob = mut_prob  # 变异概率
        self.elite_num = elite_num  # 筛选数量
        self.limit = limit  # 进化次数

        self.population = []

    def init_population(self, schedules):  # 生成初始种群
        self.population = []
        for i in range(self.pop_num):
            t = []
            for s in schedules:
                s.init_random()
                t.append(s)
            self.population.append(cp.deepcopy(t))

    def mutate(self):
        for e in self.population:
            for i in range(len(e)):
                if rd.random() < self.mut_prob:  # 达到变异概率
                    attribute = rd.randint(0, 3)  # 随机选取属性
                    if attribute == 0:
                        e[i].room_id = e[i].candidate_rooms[rd.randint(0, len(e[i].candidate_rooms) - 1)]
                    elif attribute == 1:
                        e[i].time = change(e[i].time, 1, 6)
                    elif attribute == 2:
                        e[i].day = change(e[i].day, 1, 5)
                    elif attribute == 3:
                        e[i].first_week = change(e[i].first_week, 1, 17 - e[i].week_period)

    def crossover(self, population):
        # 选取两个不同个体杂交
        e1_i = e2_i = rd.randint(0, self.elite_num - 1)
        while e2_i == e1_i:
            e2_i = rd.randint(0, self.elite_num - 1)
        e1 = population[e1_i]
        e2 = population[e2_i]
        # 交叉数据
        e = [cp.deepcopy(e1[i]) if i % 2 == 0 else cp.deepcopy(e2[i]) for i in range(len(e1))]
        return e

    def cost(self):
        conflicts = []
        n = len(self.population[0])
        for p in self.population:
            conflict = 0
            for i in range(n - 1):
                for j in range(i + 1, n):
                    # 课程时间区间重合
                    intersect = min(p[i].first_week + p[i].week_period, p[j].first_week + p[j].week_period) - \
                                max(p[i].first_week, p[j].first_week)
                    if intersect > 0:
                        # 一天上两次同一节课
                        if p[i].course_id == p[j].course_id and p[i].day == p[j].day:
                            conflict += intersect
                        # 同一时刻
                        elif p[i].time == p[j].time and p[i].day == p[j].day:
                            if p[i].room_id == p[j].room_id:  # 相同教室
                                conflict += intersect
                            if p[i].teacher_id == p[j].teacher_id:  # 相同老师
                                conflict += intersect
            conflicts.append(conflict)
        index = np.array(conflicts).argsort()  # 按照冲突大小排序种群下标
        return index, conflicts[index[0]]

    def evolution(self):
        msg = ""
        for time in range(1, self.limit + 1):
            elite_index, best = self.cost()
            bs = '\b' * len(msg)
            msg = f"times:{time}, best:{best}"
            print(bs + msg, end="")
            if best == 0:
                print("")
                return self.population[elite_index[0]]
            new_population = [self.population[index] for index in elite_index[:self.elite_num]]  # 筛选种群
            children = [self.crossover(new_population) for i in range(self.pop_num - self.elite_num)]  # 杂交子代
            self.population = new_population + children  # 更新种群
            self.mutate()  # 种群变异
        print("")
        return None
